#ifndef MUDUO_NET_SOCKETSOPS_H
#define MUDUO_NET_SOCKETSOPS_H

#include "base/Types.h"

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>  // snprintf
#include <strings.h>  // bzero
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/tcp.h>

namespace zeus
{

namespace socket
{

inline const sockaddr * sockaddr_cast(const struct sockaddr_in* addr)
{
	return (sockaddr *) (addr);
}

inline sockaddr * sockaddr_cast(struct sockaddr_in* addr)
{
	return (sockaddr *) (addr);
}

inline void set_non_block(int32 fd)
{
	int32 flags = ::fcntl(fd, F_GETFL, 0);
	flags |= O_NONBLOCK;
	int32 ret = ::fcntl(fd, F_SETFL, flags);

	flags = ::fcntl(fd, F_GETFD, 0);
	flags |= FD_CLOEXEC;
	ret = ::fcntl(fd, F_SETFD, flags);
}

inline int32 create_socket()
{
	int32 fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	set_non_block(fd);

	return fd;
}

inline int32 listen(int32 fd)
{
	int32 ret = ::listen(fd, SOMAXCONN);

	return ret;
}

inline int32 bind(int32 fd, sockaddr_in & addr)
{
	return ::bind(fd, sockaddr_cast(&addr), sizeof(addr));
}

inline int32 accept(int32 sockfd, struct sockaddr_in* addr)
{
	socklen_t addrlen = sizeof *addr;

	int32 connfd = ::accept4(sockfd, sockaddr_cast(addr), &addrlen,
			SOCK_NONBLOCK | SOCK_CLOEXEC);

	return connfd;
}

inline int32 connect(int32 sockfd, const struct sockaddr_in& addr)
{
	return ::connect(sockfd, sockaddr_cast(&addr), sizeof addr);
}

inline void set_tcp_no_delay(int32 fd, bool on)
{
	int32 optval = on ? 1 : 0;
	::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);
}

inline void set_reuse_addr(int32 fd, bool on)
{
	int32 optval = on ? 1 : 0;
	::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
}

inline ssize_t read(int32 sockfd, void *buf, size_t count)
{
	return ::read(sockfd, buf, count);
}

inline ssize_t readv(int32 sockfd, const struct iovec *iov, int32 iovcnt)
{
	return ::readv(sockfd, iov, iovcnt);
}

inline ssize_t write(int32 sockfd, const void *buf, size_t count)
{
	return ::write(sockfd, buf, count);
}
inline void close(int32 sockfd)
{
	if (::close(sockfd) < 0)
	{
	}
}

inline void shut_down_write(int32 sockfd)
{
	if (::shutdown(sockfd, SHUT_WR) < 0)
	{
	}
}

inline void to_host_port(char* buf, size_t size, const struct sockaddr_in& addr)
{
	char host[INET_ADDRSTRLEN] = "INVALID";
	::inet_ntop(AF_INET, &addr.sin_addr, host, sizeof host);
	uint16_t port = ntohs(addr.sin_port);
	snprintf(buf, size, "%s:%u", host, port);
}

inline void from_host_port(const char* ip, uint16_t port, struct sockaddr_in* addr)
{
	addr->sin_family = AF_INET;
	addr->sin_port = htons(port);
	if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
	{
	}
}

inline int32 get_socket_err(int32 sockfd)
{
	int32 optval;
	socklen_t optlen = sizeof optval;

	if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
	{
		return errno;
	}
	else
	{
		return optval;
	}
}

inline struct sockaddr_in get_loal_addr(int32 sockfd)
{
	struct sockaddr_in localaddr;
	bzero(&localaddr, sizeof localaddr);
	socklen_t addrlen = sizeof(localaddr);
	if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0)
	{
	}
	return localaddr;
}

inline struct sockaddr_in get_peer_addr(int32 sockfd)
{
	struct sockaddr_in peeraddr;
	bzero(&peeraddr, sizeof peeraddr);
	socklen_t addrlen = sizeof(peeraddr);
	if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0)
	{
	}
	return peeraddr;
}

inline bool is_self_connect(int32 sockfd)
{
	struct sockaddr_in localaddr = get_loal_addr(sockfd);
	struct sockaddr_in peeraddr = get_peer_addr(sockfd);

	return localaddr.sin_port == peeraddr.sin_port
			&& localaddr.sin_addr.s_addr == peeraddr.sin_addr.s_addr;
}

}
}
#endif  // MUDUO_NET_SOCKETSOPS_H
